package com.xxl.job.executor.datagenerator;

import com.opencsv.CSVWriter;
import java.io.FileWriter;
import java.io.IOException;
import java.time.LocalDate;
import java.time.ZoneId;
import java.util.Date;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

public class RepaymentPlanDataGenerator {

    private static final String CSV_FILE_PATH = "repayment_plan.csv";
    private static final int TOTAL_RECORDS = 10_000_000; // 1000w
    private static final int BATCH_SIZE = 500_000; // 每批生成数量
    private static final int THREAD_COUNT = Runtime.getRuntime().availableProcessors() * 2; // 线程数
    private static final Random random = new Random();
    private static final AtomicInteger currentCount = new AtomicInteger(0);

    public static void main(String[] args) {
        try (CSVWriter writer = new CSVWriter(new FileWriter(CSV_FILE_PATH))) {
            // 写入表头
            writer.writeNext(getHeader());
            writer.flush();

            // 创建线程池
            ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT);

            // 计算总批次数
            int totalBatches = TOTAL_RECORDS / BATCH_SIZE + (TOTAL_RECORDS % BATCH_SIZE > 0 ? 1 : 0);

            // 提交任务
            for (int i = 0; i < totalBatches; i++) {
                int batchIndex = i;
                executor.submit(() -> generateBatch(writer, batchIndex));
            }

            // 关闭线程池并等待完成
            executor.shutdown();
            while (!executor.isTerminated()) {
                // 等待所有任务完成
            }

            System.out.println("✅ 数据生成完成，文件路径: " + CSV_FILE_PATH);

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    private static String[] getHeader() {
        return new String[]{
                "id",
                "loan_id",
                "user_id",
                "total_term",
                "current_term",
                "due_date",
                "actual_repay_date",
                "overdue_days",
                "principal_amount",
                "interest_amount",
                "fee_amount",
                "total_amount",
                "repaid_amount",
                "status",
                "is_overdue",
                "created_at",
                "updated_at"
        };
    }

    private static void generateBatch(CSVWriter writer, int batchIndex) {
        int start = batchIndex * BATCH_SIZE;
        int end = Math.min(start + BATCH_SIZE, TOTAL_RECORDS);

        for (int i = start; i < end; i++) {
            try {
                writer.writeNext(generateRow(i));

                int progress = currentCount.incrementAndGet();
                if (progress % 100_000 == 0) {
                    System.out.println("已生成 " + progress + " 条数据...");
                }

                if (i % 10_000 == 0) {
                    writer.flush();
                }
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }

    private static String[] generateRow(int index) {
        long id = index + 1;
        String loanId = "LOAN" + String.format("%08d", index);
        String userId = "USER" + String.format("%06d", random.nextInt(999999));
        int totalTerm = 12;
        int currentTerm = random.nextInt(totalTerm) + 1;
        Date dueDate = Date.from(getRandomDate(2023, 2024).atStartOfDay(ZoneId.systemDefault()).toInstant());
        Date actualRepayDate = null;
        int overdueDays = 0;
        boolean isOverdue = false;

        if (new Date().after(dueDate)) {
            overdueDays = random.nextInt(30);
            isOverdue = true;
            // 部分还款
            if (random.nextDouble() < 0.3) {
                actualRepayDate = Date.from(getRandomDateAfter(dueDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate(), 1, 15)
                        .atStartOfDay(ZoneId.systemDefault()).toInstant());
            }
        }

        double principal = round(random.nextDouble() * 5000 + 1000);
        double interest = round(principal * 0.05);
        double fee = round(principal * 0.01);
        double total = round(principal + interest + fee);
        double repaid = 0;
        String status = "pending";

        if (actualRepayDate != null) {
            repaid = total;
            status = "paid";
        } else if (isOverdue) {
            status = "overdue";
        }

        return new String[]{
                String.valueOf(id),
                loanId,
                userId,
                "12",
                String.valueOf(currentTerm),
                dueDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString(),
                actualRepayDate != null ? actualRepayDate.toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString() : "",
                String.valueOf(overdueDays),
                String.valueOf(principal),
                String.valueOf(interest),
                String.valueOf(fee),
                String.valueOf(total),
                String.valueOf(repaid),
                status,
                String.valueOf(isOverdue),
                Date.from(getRandomDate(2022, 2023).atStartOfDay(ZoneId.systemDefault()).toInstant()).toInstant().atZone(ZoneId.systemDefault()).toLocalDate().toString(),
                LocalDate.now().toString()
        };
    }

    private static LocalDate getRandomDate(int startYear, int endYear) {
        if (startYear > endYear) {
            throw new IllegalArgumentException("Start year cannot be greater than end year");
        }
        int year = random.nextInt(endYear - startYear + 1) + startYear;
        int dayOfYear = random.nextInt(365) + 1;
        return LocalDate.ofYearDay(year, dayOfYear);
    }

    private static LocalDate getRandomDateAfter(LocalDate base, int minDays, int maxDays) {
        if (base == null) {
            throw new IllegalArgumentException("Base date cannot be null");
        }
        int daysToAdd = random.nextInt(maxDays - minDays + 1) + minDays;
        return base.plusDays(daysToAdd);
    }

    private static double round(double value) {
        return Math.round(value * 100.0) / 100.0;
    }
}
